package com.powernode.listener;

import com.powernode.service.GoodsService;
import org.apache.rocketmq.common.message.MessageExt;
import org.apache.rocketmq.spring.annotation.ConsumeMode;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.Duration;

@Component
@RocketMQMessageListener(topic = "seckillTopic",
        consumerGroup = "seckill-producer-group",
        consumeMode = ConsumeMode.CONCURRENTLY,
        consumeThreadNumber = 16
)
public class SeckillListener implements RocketMQListener<MessageExt> {

    @Autowired
    private GoodsService goodsService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    // 自旋
    int ZX_TIME = 10000;    // 10s

    /**
     * 目标: 扣减库存, 写订单表 -- 保证扣减库存和订单数一致
     * 方案三: redis分布式锁(setnx)+自旋
     *  推荐: 压力会分摊到redis和程序中执行,缓解db的压力
     * @param message
     */
    @Override
    public void onMessage(MessageExt message) {
        String msg = new String(message.getBody());
        int userId = Integer.parseInt(msg.split("-")[0]);
        int goodsId = Integer.parseInt(msg.split("-")[1]);

        int currentThreadTime = 0;
        while (currentThreadTime < ZX_TIME) {
            // 设置key的过期时间,可避免死锁的发生
            Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock:" + goodsId, "", Duration.ofSeconds(30));
            if (flag) {
                // 成功获取锁
                try {
                    goodsService.realSeckill1(userId, goodsId);
                    return;
                } finally {
                    // 删除
                    redisTemplate.delete("lock:" + goodsId);
                }
            } else {
                currentThreadTime += 200;
                try {
                    Thread.sleep(200L);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 目标: 扣减库存, 写订单表 -- 保证扣减库存和订单数一致
     * 方案二: 分布式锁 -- 利用MySQL的行锁
     *    缺点: 当并发量很大时,数据库的压力很大,性能可能会受到很大影响
     * @param message
     */
    /*@Override
    public void onMessage(MessageExt message) {
        String msg = new String(message.getBody());
        int userId = Integer.parseInt(msg.split("-")[0]);
        int goodsId = Integer.parseInt(msg.split("-")[1]);
        goodsService.realSeckill1(userId, goodsId);
    }*/

    /**
     * 目标: 扣减库存, 写订单表 -- 保证扣减库存和订单数一致
     * 方案一：在事务外面加锁
     *      缺点: 不满足分布式要求, synchronized只适合单机版,不适合集群
     * @param message
     */
    /*@Override
    public void onMessage(MessageExt message) {
        String msg = new String(message.getBody());
        // 消息格式: userId + "-" + goodsId
        int userId = Integer.parseInt(msg.split("-")[0]);
        int goodsId = Integer.parseInt(msg.split("-")[1]);

        // 解决减库存数和订单数不一致 -- 在事务外面加锁,可实现安全
        // 当进程在排队时,在jvm中的EntrySet、WaitSet两个队列等待
        synchronized (this) {
            goodsService.realSeckill(userId, goodsId);
        }
    }*/
}
